package cn.kivensoft.xlsx.reader;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class Workbook {
    private List<Sheet> sheets = new ArrayList<Sheet>();
    private Map<Integer, String> sharedStrings = new HashMap<Integer, String>();
    private Map<Integer, NumberFormat> numberFormats = new HashMap<Integer, NumberFormat>();
    private WorkbookOptions options = new WorkbookOptions();
    private int baseYear = 1900;
    
    public Workbook(String fileName, WorkbookOptions options) throws Exception {
        if (options != null) this.options = options;
        
        try {
        	File f = new File(fileName);
        	byte[] buffer = new byte[(int)f.length()];
        	InputStream is = new FileInputStream(fileName);
        	try {
        		is.read(buffer);
			} finally {
				is.close();
				is = null;
			}
			open(buffer);
        }
        catch (Exception e) {
            throw new Exception("打开文件失败！", e);
        }
    }

    public Workbook(InputStream is, int size, WorkbookOptions options) throws Exception {
        if (is == null) throw new Exception("读取文件流失败！");
        if (options != null) this.options = options;
        byte[] buffer = new byte[size];
        is.read(buffer);
        open(buffer);
    }
    
    public Workbook(byte[] buffer, WorkbookOptions options) throws Exception {
    	open(buffer);
    }

    private void open(byte[] bytes) throws Exception {
    	try {
    		SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
			loadWorkbookXml(readZipEntryContent(bytes, "xl/workbook.xml"), parser);
			loadWorkbookXmlRels(readZipEntryContent(bytes, "xl/_rels/workbook.xml.rels"), parser);
			loadSharedStrings(readZipEntryContent(bytes, "xl/sharedStrings.xml"), parser);
			loadStyles(readZipEntryContent(bytes, "xl/styles.xml"), parser);
			if (options.getLoadSheets()) {
				for (Sheet sheet : sheets) sheet.open(bytes);
			}
    	} catch (Exception e) {
    		throw new Exception("文件格式错误！", e);
    	}
    }
    
    static InputStream readZipEntryContent(byte[] bytes, String entryName) throws Exception {
    	if (entryName == null || entryName.isEmpty()) return null;
    	ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(bytes));
		for (ZipEntry ze = zis.getNextEntry(); ze != null; ze = zis.getNextEntry()) {
			if (entryName.equals(ze.getName())) return zis;
		}
		return null;
    }

    private void loadWorkbookXml(InputStream is, SAXParser parser) throws Exception {
    	if (is == null) return;
    	
    	parser.parse(is, new DefaultHandler(){
			@Override
			public void startElement(String uri, String localName,
					String qName, Attributes attributes) throws SAXException {
				if ("sheet".equals(qName)) {
        			String id = attributes.getValue("r:id");
        			String name = attributes.getValue("name");
        			boolean hidden = "hidden".equals(attributes.getValue("state"));
        			Sheet sheet = new Sheet(name, id, hidden);
        			sheet.setWorkbook(Workbook.this);
        			sheets.add(sheet);
				} else if ("workbookPr".equals(qName)) {
					String s1904 = attributes.getValue("date1904");
					if (s1904 != null && "1".equals(s1904)) baseYear = 1904;
				} else {
					//super.startElement(uri, localName, qName, attributes);
				}
			}
    	});
    }

    private void loadWorkbookXmlRels(InputStream is, SAXParser parser) throws Exception {
    	if (is == null) return;
    	
    	parser.parse(is, new DefaultHandler(){
			@Override
			public void startElement(String uri, String localName,
					String qName, Attributes attributes) throws SAXException {
				if ("Relationship".equals(qName)) {
        			String id = attributes.getValue("Id");
        			String target = attributes.getValue("Target");
        			if (id != null && !id.isEmpty() && target != null && !target.isEmpty()) {
						for (Sheet s : sheets) {
							if (id.equals(s.getId())) {
								if (target.startsWith("worksheets/"))
									s.setPath("xl/" + target);
								else
									s.setPath(target.substring(1)); //去除开头的'/'字符
								break;
							}
						}
        			}
				} else {
					//super.startElement(uri, localName, qName, attributes);
				}
			}
    	});
    }

    private void loadSharedStrings(InputStream is, SAXParser parser) throws Exception {
        // The xl/sharedStrings.xml file will be missing if there are no strings
        if (is == null) return;
        
    	parser.parse(is, new DefaultHandler() {
    		boolean _find_si = false;
    		boolean _find_t = false;
    		int count = 0;
    		StringBuilder sb = new StringBuilder(128);
			@Override
			public void startElement(String uri, String localName,
					String qName, Attributes attributes) throws SAXException {
				if ("t".equals(qName)) {
					_find_t = true;
				} else if ("si".equals(qName)) {
					_find_si = true;
				} else {
					//super.startElement(uri, localName, qName, attributes);
				}
			}
			@Override
			public void endElement(String uri, String localName, String qName)
					throws SAXException {
				if ("t".equals(qName)) {
					if (_find_si && _find_t) {
						_find_t = false;
						sharedStrings.put(count++, sb.toString());
						sb.delete(0, sb.length());
					}
				} else if ("si".equals(qName)) {
					_find_si = false;
				}
			}
			@Override
			public void characters(char[] ch, int start, int length)
					throws SAXException {
				if (_find_si && _find_t) {
					sb.append(ch, start, length);
				}
			}
    	});
    }

    private void loadStyles(InputStream is, SAXParser parser) throws Exception {
    	if (is == null) return;

    	parser.parse(is, new DefaultHandler() {
    		int index = 0;
			@Override
			public void startElement(String uri, String localName,
					String qName, Attributes attributes) throws SAXException {
				if ("xf".equals(qName)) {
					String numFmtId = attributes.getValue("numFmtId");
					if (numFmtId == null || numFmtId.isEmpty()) return;
					int numberFormatId = Integer.parseInt(numFmtId);
					
					NumberFormat _nf = NumberFormat.General;
					switch(numberFormatId) {
						case 0: _nf = NumberFormat.General; break;
						case 2: _nf = NumberFormat.Number; break;
						case 164: _nf = NumberFormat.Currency; break;
						case 44: _nf = NumberFormat.Accounting; break;
						case 14: _nf = NumberFormat.Date; break;
						case 165: _nf = NumberFormat.Time; break;
						case 22: _nf = NumberFormat.DateTime; break;
						case 49: _nf = NumberFormat.Text; break;
						case 10: _nf = NumberFormat.Percentage; break;
						case 13: _nf = NumberFormat.Fraction; break;
						case 166: _nf = NumberFormat.Custom; break;
						case 11: _nf = NumberFormat.Scientific; break;
						default: _nf = NumberFormat.Unsupported; break;
					}

					numberFormats.put(index++, _nf);
				} else {
					//super.startElement(uri, localName, qName, attributes);
				}
			}
    	});
    }
    
    public List<Sheet> getSheets() {
    	return sheets;
    }

    public Sheet sheet(String name) {
    	if (name == null || name.isEmpty()) return null;
    	for (Sheet s : sheets)
    		if (name.equals(s.getName())) return s;
    	return null;
    }

    public WorkbookOptions getOptions() {
        return options;
    }

    public void setOptions(WorkbookOptions value) {
        options = value;
    }

    public Map<Integer, String> getSharedStrings() {
        return sharedStrings;
    }

    public Map<Integer, NumberFormat> getNumberFormats() {
        return numberFormats;
    }

    public int getBaseYear() {
        return baseYear;
    }

}


